// Copyright (C) Calum Grant 2008

#include "cppscript"
#include "dynamic/extensions.hpp"
#include "fstat.hpp"

#include <sstream>
#include <fstream>
#include <iostream>
#include <iomanip>


void dynamic::writeln(const var & v)
{
	std::cout << v << std::endl;
}


var dynamic::readln()
{
	std::string line;
	{
		internal::leave_apartment l;
		std::getline(std::cin, line);
	}
	return line;
}


void dynamic::writeln()
{
	std::cout << std::endl;
}


void dynamic::write(const var & v)
{
	std::cout << v;
}


namespace
{
	namespace File
	{
		void check_valid_file(std::iostream & s)
		{
			if(!s) throw exception("io_error");
		}

		void writeln(var file, var line)
		{
			internal::leave_apartment l;
			std::iostream & os = file["iostream"].as<std::iostream>();
			os << line << std::endl;
			check_valid_file(os);
		}

		void close(var file)
		{
			as_derived<std::iostream, std::fstream>(file["iostream"]).close();
		}

		var readln(var file)
		{
			std::iostream & stream = file["iostream"].as<std::iostream>();
			check_valid_file(stream);
			std::string str;
			{
				internal::leave_apartment l;
				std::getline(stream, str);
			}
			if(!stream) return null;
			return str;
		}

		var readch(var file)
		{
			std::iostream & stream = file["iostream"].as<std::iostream>();
			check_valid_file(stream);
			int ch;
			{
				internal::leave_apartment l;
				ch = stream.get();
			}
			return ch==-1 ? null : var(char(ch));
		}

		var read(var file)
		{
			std::iostream & stream = file["iostream"].as<std::iostream>();
			check_valid_file(stream);
			var str = "";
			const int BUFFER_SIZE=16384;
			char buffer[BUFFER_SIZE];

			while(stream)
			{
				{
					internal::leave_apartment l;
					stream.read(buffer, BUFFER_SIZE);
				}
				str += string_it(buffer, buffer+stream.gcount());
			}

			return str;
		}

		void write(var file, var str)
		{
			// leave_apartment l;
			std::iostream & os = file["iostream"].as<std::iostream>();
			check_valid_file(os);
			os << str;
		}

		void rewind(var file)
		{
			std::iostream & stream = file["iostream"].as<std::iostream>();
			stream.clear();
			stream.seekg(0, std::ios::beg);
			check_valid_file(stream);
		}
	}


	namespace cout
	{
		void writeln(var l) 
		{ 
			std::cout << l << std::endl; 
		}

		void write(var l) 
		{ 
			std::cout << l; 
		}

		void writech(var c) 
		{ 
			std::cout << char(c.as_int()); 
		}
	}


	namespace cerr
	{
		void writeln(var l) 
		{ 
			std::cerr << l << std::endl; 
		}

		void write(var l) 
		{ 
			std::cerr << l; 
		}

		void writech(var c) 
		{ 
			std::cerr << char(c.as_int()); 
		}
	}


	namespace cin
	{
		var readln() 
		{ 			
			std::string str;
			{
				internal::leave_apartment l;
				std::getline(std::cin, str);
			}
			if(!std::cin) return null;
			return str;
		}

		var read(var bytes)
		{
			std::size_t size = bytes.as_int();
			std::vector<char> buffer(size);
			{
				internal::leave_apartment l;
				std::cin.read(&buffer[0], size);
			}
			if(!std::cin) return null;
			return string_it(buffer.begin(), buffer.begin()+std::cin.gcount());
		}

		var readch()
		{
			int ch;
			{
				internal::leave_apartment l;
				ch = std::cin.get();
			}
			return ch==-1 ? null : var(char(ch));
		}
	}


	var stream()
	{
		return object("stream").extend
			("writeln", File::writeln)
			("readch", File::readch)
			("readln", File::readln)
			("write", File::write)
			("read", File::read);
	}

	var file_stream()
	{
		return stream().extend
			("close", File::close)
			("rewind", File::rewind);
	}
}


var dynamic::write_file(const var & filename)
{
	var file = file_stream().extend
		("iostream", create_base<std::iostream,std::fstream>() );

	as_derived<std::iostream, std::fstream>(file["iostream"]).open(filename.as_string().c_str(), std::ios::out);

	File::check_valid_file( file["iostream"].as<std::iostream>() );

	return file;

}


var dynamic::open_file(const var & filename, std::ios_base::openmode flags)
{
	var file = file_stream().extend
		("iostream", create_base<std::iostream,std::fstream>() );

	as_derived<std::iostream, std::fstream>(file["iostream"]).open(filename.as_string().c_str(), flags );
	
	File::check_valid_file( file["iostream"].as<std::iostream>() );

	return file;
}


var dynamic::read_file(const var & filename)
{
	var file = file_stream().extend
		("iostream", create_base<std::iostream,std::fstream>() );

	as_derived<std::iostream, std::fstream>(file["iostream"]).open(filename.as_string().c_str(), std::ios::in);

	File::check_valid_file( file["iostream"].as<std::iostream>() );

	return file;
}


var dynamic::mem_file()
{
	return file_stream().extend
		("iostream", create_base<std::iostream,std::stringstream>() );
}


var dynamic::out()
{
	return object().extend
		("writeln", var(cout::writeln))
		("write", var(cout::write))
		("writech", var(cout::writech));
}


var dynamic::err()
{
	return object().extend
		("writeln", var(cerr::writeln))
		("write", var(cerr::write))
		("writech", var(cerr::writech));
}


var dynamic::in()
{
	return object().extend
		("readln", var(cin::readln))
		("readch", var(cin::readch))
		("read", var(cin::read));
}


var dynamic::fstat(const var & filename)
{
	api::stat buf;
	int result = api::fstat(filename.as_string().c_str(), &buf);

	if(result)
	{
		return var();
	}
	else
	{
		return object("fstat").extend
			("size", int(buf.st_size))
			("modified", int(buf.st_mtime));
	}
}


void dynamic::pickle_file(const var & filename, const var & data)
{
	var file = write_file(filename);
	finally(file["close"])
	file["writeln"](pickle(data));
}


var dynamic::unpickle_file(const var & filename)
{
	var file = read_file(filename);
	finally( file["close"] );

	var data = file["readln"]();	/// \todo Read directly from stream
	return data ? unpickle(data.as_string()) : var();
}


var dynamic::characters(const var & file)
{
	return enumerate( file["readch"] );
}


var dynamic::lines(const var & file)
{
	return enumerate( file["readln"] );
}
